// ============================================================================
//
// LCP-net framework by Pierre CHATEL chatelp@gmail.com
// Distributed under GPLv3
// Copyright (C) THALES Land & Joint Systems, 2008, 2009. All rights reserved
//
// ============================================================================

/**
 * <copyright>
 * </copyright>
 *
 * $Id$
 */
package com.thalesgroup.preferenceFramework.models.LCPnet.impl;

import java.util.Collection;
import java.util.logging.Logger;

import net.sourceforge.jFuzzyLogic.FIS;
import net.sourceforge.jFuzzyLogic.membership.MembershipFunction;
import net.sourceforge.jFuzzyLogic.membership.MembershipFunctionPieceWiseLinear;
import net.sourceforge.jFuzzyLogic.rule.FuzzyRuleSet;
import net.sourceforge.jFuzzyLogic.rule.LinguisticTerm;
import net.sourceforge.jFuzzyLogic.rule.Variable;

import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.NotificationChain;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.impl.ENotificationImpl;
import org.eclipse.emf.ecore.util.EObjectResolvingEList;

import com.thalesgroup.preferenceFramework.models.Node;
import com.thalesgroup.preferenceFramework.models.NodeValue;
import com.thalesgroup.preferenceFramework.models.LCPnet.CIArc;
import com.thalesgroup.preferenceFramework.models.LCPnet.CIT;
import com.thalesgroup.preferenceFramework.models.LCPnet.CITLine;
import com.thalesgroup.preferenceFramework.models.LCPnet.CNodeValue;
import com.thalesgroup.preferenceFramework.models.LCPnet.COutcome;
import com.thalesgroup.preferenceFramework.models.LCPnet.Coordinate;
import com.thalesgroup.preferenceFramework.models.LCPnet.LCPnetPackage;
import com.thalesgroup.preferenceFramework.models.LCPnet.LNode;
import com.thalesgroup.preferenceFramework.models.LCPnet.LinguisticDomain;
import com.thalesgroup.preferenceFramework.models.LCPnet.LinguisticSubset;
import com.thalesgroup.preferenceFramework.models.LCPnet.exceptions.LCPnetEvaluationException;
import com.thalesgroup.preferenceFramework.models.impl.ArcImpl;

/**
 * <!-- begin-user-doc --> An implementation of the model object '
 * <em><b>CI Arc</b></em>'. <!-- end-user-doc -->
 * <p>
 * The following features are implemented:
 * <ul>
 *   <li>{@link com.thalesgroup.preferenceFramework.models.LCPnet.impl.CIArcImpl#getSelectorSet <em>Selector Set</em>}</li>
 *   <li>{@link com.thalesgroup.preferenceFramework.models.LCPnet.impl.CIArcImpl#getImportanceTable <em>Importance Table</em>}</li>
 * </ul>
 * </p>
 *
 * @generated
 */
public class CIArcImpl extends ArcImpl implements CIArc {
	private static Logger logger = Logger.getLogger(CIArcImpl.class.getName());
	private FIS fis = null;

	/**
	 * The cached value of the '{@link #getSelectorSet() <em>Selector Set</em>}' reference list.
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @see #getSelectorSet()
	 * @generated
	 * @ordered
	 */
	protected EList<LNode> selectorSet;

	/**
	 * The cached value of the '{@link #getImportanceTable() <em>Importance Table</em>}' containment reference.
	 * <!-- begin-user-doc
	 * --> <!-- end-user-doc -->
	 * @see #getImportanceTable()
	 * @generated
	 * @ordered
	 */
	protected CIT importanceTable;

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	protected CIArcImpl() {
		super();
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	@Override
	protected EClass eStaticClass() {
		return LCPnetPackage.Literals.CI_ARC;
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	public EList<LNode> getSelectorSet() {
		if (selectorSet == null) {
			selectorSet = new EObjectResolvingEList<LNode>(LNode.class, this, LCPnetPackage.CI_ARC__SELECTOR_SET);
		}
		return selectorSet;
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	public CIT getImportanceTable() {
		return importanceTable;
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	public NotificationChain basicSetImportanceTable(CIT newImportanceTable,
			NotificationChain msgs) {
		CIT oldImportanceTable = importanceTable;
		importanceTable = newImportanceTable;
		if (eNotificationRequired()) {
			ENotificationImpl notification = new ENotificationImpl(this, Notification.SET, LCPnetPackage.CI_ARC__IMPORTANCE_TABLE, oldImportanceTable, newImportanceTable);
			if (msgs == null) msgs = notification; else msgs.add(notification);
		}
		return msgs;
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	public void setImportanceTable(CIT newImportanceTable) {
		if (newImportanceTable != importanceTable) {
			NotificationChain msgs = null;
			if (importanceTable != null)
				msgs = ((InternalEObject)importanceTable).eInverseRemove(this, LCPnetPackage.CIT__ARC, CIT.class, msgs);
			if (newImportanceTable != null)
				msgs = ((InternalEObject)newImportanceTable).eInverseAdd(this, LCPnetPackage.CIT__ARC, CIT.class, msgs);
			msgs = basicSetImportanceTable(newImportanceTable, msgs);
			if (msgs != null) msgs.dispatch();
		}
		else if (eNotificationRequired())
			eNotify(new ENotificationImpl(this, Notification.SET, LCPnetPackage.CI_ARC__IMPORTANCE_TABLE, newImportanceTable, newImportanceTable));
	}

	public EList<LNode> getArcDirection(EList<CNodeValue> nodeValues) throws LCPnetEvaluationException {
		FIS fis = this.getFuzzyInferenceSystem();
		FuzzyRuleSet fuzzyRuleSet = fis.getFuzzyRuleSet();

		// 1 - Set node values in fuzzy rule set
		for (CNodeValue nodeValue : nodeValues) {
			Node node = nodeValue.getNode();
			//add variable to rule set only if in selector set
			if(this.getSelectorSet().contains(node)) {
				String variableName = nodeValue.getNode().getName();
				Double variableValue = nodeValue.getCrispValue();
				fuzzyRuleSet.setVariable(variableName, variableValue);
			}
		}

		// 2 - Reset rule set and run evaluation
		fuzzyRuleSet.evaluate();
		// System.out.println(fuzzyRuleSet);

		// 3 - Find CIT line with best score
		double bestScore = -1;
		CITLine bestLine = null;
		EList<CITLine> lines = this.getImportanceTable().getLines();
		for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
			CITLine line = lines.get(lineNumber);
			
			double score = fuzzyRuleSet.getVariable("score_line_"+lineNumber).getLatestDefuzzifiedValue();
			if(score > bestScore) {
				bestScore = score;
				bestLine = line;
			}	
		}
		
		if(bestLine != null) {
			EList<LNode> arcDirection = new BasicEList<LNode>();
			arcDirection.add(bestLine.getStartNode());
			arcDirection.add(bestLine.getEndNode());
			
			return arcDirection;
		}
		else {
			throw new LCPnetEvaluationException("Cannot compute CIArc direction: "+ this.getName());
		}
	}

	public EList<LNode> getArcDirection(COutcome outcome) throws LCPnetEvaluationException {
		COutcome coutcome = (COutcome) outcome;
		EList<NodeValue> nodeValues = coutcome.getNodeValues();
		EList<CNodeValue> cNodeValues = new BasicEList<CNodeValue>();
		for (NodeValue nodeValue : nodeValues) {
			cNodeValues.add((CNodeValue) nodeValue);
		}
		
		return this.getArcDirection(cNodeValues);
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	@Override
	public NotificationChain eInverseAdd(InternalEObject otherEnd,
			int featureID, NotificationChain msgs) {
		switch (featureID) {
			case LCPnetPackage.CI_ARC__IMPORTANCE_TABLE:
				if (importanceTable != null)
					msgs = ((InternalEObject)importanceTable).eInverseRemove(this, EOPPOSITE_FEATURE_BASE - LCPnetPackage.CI_ARC__IMPORTANCE_TABLE, null, msgs);
				return basicSetImportanceTable((CIT)otherEnd, msgs);
		}
		return super.eInverseAdd(otherEnd, featureID, msgs);
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	@Override
	public NotificationChain eInverseRemove(InternalEObject otherEnd,
			int featureID, NotificationChain msgs) {
		switch (featureID) {
			case LCPnetPackage.CI_ARC__IMPORTANCE_TABLE:
				return basicSetImportanceTable(null, msgs);
		}
		return super.eInverseRemove(otherEnd, featureID, msgs);
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	@Override
	public Object eGet(int featureID, boolean resolve, boolean coreType) {
		switch (featureID) {
			case LCPnetPackage.CI_ARC__SELECTOR_SET:
				return getSelectorSet();
			case LCPnetPackage.CI_ARC__IMPORTANCE_TABLE:
				return getImportanceTable();
		}
		return super.eGet(featureID, resolve, coreType);
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void eSet(int featureID, Object newValue) {
		switch (featureID) {
			case LCPnetPackage.CI_ARC__SELECTOR_SET:
				getSelectorSet().clear();
				getSelectorSet().addAll((Collection<? extends LNode>)newValue);
				return;
			case LCPnetPackage.CI_ARC__IMPORTANCE_TABLE:
				setImportanceTable((CIT)newValue);
				return;
		}
		super.eSet(featureID, newValue);
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	@Override
	public void eUnset(int featureID) {
		switch (featureID) {
			case LCPnetPackage.CI_ARC__SELECTOR_SET:
				getSelectorSet().clear();
				return;
			case LCPnetPackage.CI_ARC__IMPORTANCE_TABLE:
				setImportanceTable((CIT)null);
				return;
		}
		super.eUnset(featureID);
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	@Override
	public boolean eIsSet(int featureID) {
		switch (featureID) {
			case LCPnetPackage.CI_ARC__SELECTOR_SET:
				return selectorSet != null && !selectorSet.isEmpty();
			case LCPnetPackage.CI_ARC__IMPORTANCE_TABLE:
				return importanceTable != null;
		}
		return super.eIsSet(featureID);
	}

	private FIS getFuzzyInferenceSystem() throws LCPnetEvaluationException {
		// Singleton pattern : the FIS doesn't need to be recalculated each time
		if (fis == null) {
			fis = new FIS();

			// only one ruleSet for a node
			FuzzyRuleSet ruleSet = new FuzzyRuleSet();

			// initialize rule set
			this.setInputVariables(ruleSet);
			this.getImportanceTable().setOutputVariables(ruleSet);
			this.getImportanceTable().setRules(ruleSet);

			fis.addFuzzyRuleSet("CIArc " + this.getName(), ruleSet);
		}
		return fis;
	}

	private void setInputVariables(FuzzyRuleSet ruleSet) {
		// selector set -> input variables of the fuzzy rule set
		EList<LNode> nodesToBeAdded = this.getSelectorSet();

		for (LNode lnode : nodesToBeAdded) {
			String nodeName = lnode.getName();
			if (!ruleSet.varibleExists(nodeName)) {
				// 1 - create corresponding input variable declaration as a Real
				// (node name = variable name)
				Variable inputVariable = new Variable(nodeName);

				// 2 - create corresponding input variable fuzzy definition
				LinguisticDomain nodeDomain = lnode.getValueDomain();
				EList<LinguisticSubset> domainSubsets = nodeDomain.getSubsets();

				// convert each fuzzy subset to a membership function
				for (LinguisticSubset subset : domainSubsets) {

					EList<Coordinate> coordinates = subset.getFuzzySubset();
					double[] x = new double[coordinates.size()];
					double[] y = new double[coordinates.size()];
					for (int i = 0; i < coordinates.size(); i++) {
						x[i] = coordinates.get(i).getX();
						y[i] = coordinates.get(i).getY();
					}

					MembershipFunction membershipFunction = new MembershipFunctionPieceWiseLinear(
							x, y);
					LinguisticTerm linguisticTerm = new LinguisticTerm(subset
							.getName(), membershipFunction);

					inputVariable.add(linguisticTerm);
				}

				// 3 - add variable to rule set
				ruleSet.getVariables().put(nodeName, inputVariable);
			} else {
				logger.warning("Variable " + nodeName + " already exists !");
			}
		}
	}
} // CIArcImpl
